/**
 * This class shouldn't be created directly use NestedSetStore or AdjacencyListStore instead.
 *
 * @abstract
 */
Ext.ux.maximgb.treegrid.AbstractTreeStore = Ext.extend(Ext.data.Store,
{
	/**
	 * @cfg {String} is_leaf_field_name Record leaf flag field name.
	 */
	leaf_field_name : '_is_leaf',
	
	/**
	 * Current page offset.
	 *
	 * @access private
	 */
	page_offset : 0,
	
	/**
	 * Current active node. 
	 *
	 * @access private
	 */
	active_node : null,
	
	/**
	 * @constructor
	 */
	constructor : function(config)
	{
		Ext.ux.maximgb.treegrid.AbstractTreeStore.superclass.constructor.call(this, config);
		
		if (!this.paramNames.active_node) {
			this.paramNames.active_node = 'anode';
		}
		
		this.addEvents(
			/**
			 * @event beforeexpandnode
			 * Fires before node expand. Return false to cancel operation.
			 * param {AbstractTreeStore} this
			 * param {Record} record
			 */
			'beforeexpandnode',
			/**
			 * @event expandnode
			 * Fires after node expand.
			 * param {AbstractTreeStore} this
			 * param {Record} record
			 */
			'expandnode',
			/**
			 * @event expandnodefailed
			 * Fires when expand node operation is failed.
			 * param {AbstractTreeStore} this
			 * param {id} Record id
			 * param {Record} Record, may be undefined 
			 */
			'expandnodefailed',
			/**
			 * @event beforecollapsenode
			 * Fires before node collapse. Return false to cancel operation.
			 * param {AbstractTreeStore} this
			 * param {Record} record
			 */
			'beforecollapsenode',
			/**
			 * @event collapsenode
			 * Fires after node collapse.
			 * param {AbstractTreeStore} this
			 * param {Record} record
			 */
			'collapsenode',
			/**
			 * @event beforeactivenodechange
			 * Fires before active node change. Return false to cancel operation.
			 * param {AbstractTreeStore} this
			 * param {Record} old active node record
			 * param {Record} new active node record
			 */
			'beforeactivenodechange',
			/**
			 * @event activenodechange
			 * Fires after active node change.
			 * param {AbstractTreeStore} this
			 * param {Record} old active node record
			 * param {Record} new active node record
			 */
			'activenodechange'
		);
	},	

	// Store methods.
	// -----------------------------------------------------------------------------------------------	
	/**
	 * Removes record and all its descendants.
	 *
	 * @access public
	 * @param {Record} record Record to remove.
	 */
	remove : function(record)
	{
		// ----- Modification start
		if (record === this.active_node) {
			this.setActiveNode(null);
		}
		this.removeNodeDescendants(record);
		// ----- End of modification		
		Ext.ux.maximgb.treegrid.AbstractTreeStore.superclass.remove.call(this, record);
	},
	
	/**
	 * Removes node descendants.
	 *
	 * @access private
	 */
	removeNodeDescendants : function(rc)
	{
		var i, len, children = this.getNodeChildren(rc);
		for (i = 0, len = children.length; i < len; i++) {
			this.remove(children[i]);
		}
	},
	
	/**
	 * Applyes current sort method.
	 *
	 * @access private
	 */
	applySort : function()
	{
		if(this.sortInfo && !this.remoteSort){
			var s = this.sortInfo, f = s.field;
			this.sortData(f, s.direction);
		}
		// ----- Modification start
		else {
			this.applyTreeSort();
		}
		// ----- End of modification
	},
	
	/**
	 * Sorts data according to sort params and then applyes tree sorting.
	 *
	 * @access private
	 */
	sortData : function(f, direction) 
	{
		direction = direction || 'ASC';
		var st = this.fields.get(f).sortType;
		var fn = function(r1, r2){
			var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
			return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
		};
		this.data.sort(direction, fn);
		if(this.snapshot && this.snapshot != this.data){
			this.snapshot.sort(direction, fn);
		}
		// ----- Modification start
		this.applyTreeSort();
		// ----- End of modification
	},
	
	/**
	 * Loads current active record data.
	 */
	load : function(options)
	{
		if (options) {
			if (options.params) {
				if (options.params[this.paramNames.active_node] === undefined) {
					options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
				}
			}
			else {
				options.params = {};
				options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
			}
		}
		else {
			options = {params: {}};
			options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
		}
		if (options.params[this.paramNames.active_node] !== null) {
			options.add = true;
		}
		return Ext.ux.maximgb.treegrid.AbstractTreeStore.superclass.load.call(this, options); 
	},
	
	/**
	 * Called as a callback by the Reader during load operation.
	 *
	 * @access private
	 */
	loadRecords : function(o, options, success)
	{
    if (!o || success === false) {
      if (success !== false) {
        this.fireEvent("load", this, [], options);
      }
      if (options.callback) {
        options.callback.call(options.scope || this, [], options, false);
      }
      return;
    }
    
    var r = o.records, t = o.totalRecords || r.length,  
    		page_offset = this.getPageOffsetFromOptions(options),
    		loaded_node_id = this.getLoadedNodeIdFromOptions(options), 
    		loaded_node, i, len, self = this;
    
    if (!options || options.add !== true || loaded_node_id === null) {
      if (this.pruneModifiedRecords) {
        this.modified = [];
      }
      for (var i = 0, len = r.length; i < len; i++) {
        r[i].join(this);
      }
      if (this.snapshot) {
        this.data = this.snapshot;
        delete this.snapshot;
      }
      this.data.clear();
      this.data.addAll(r);
      this.page_offset = page_offset;
      this.totalLength = t;
      this.applySort();
      this.fireEvent("datachanged", this);
    } else {
    	loaded_node = this.getById(loaded_node_id);
    	if (loaded_node) {
    		this.setNodeChildrenOffset(loaded_node, page_offset);
    		this.setNodeChildrenTotalCount(loaded_node, Math.max(t, r.length));
    		
				this.removeNodeDescendants(loaded_node);
				this.suspendEvents();
				for (i = 0, len = r.length; i < len; i++) {
      		this.add(r[i]);
      	}
      	this.applySort();
      	this.resumeEvents();
      	idx = [];
      	
      	r.sort(function(r1, r2) {
      		var idx1 = self.data.indexOf(r1),
      				idx2 = self.data.indexOf(r2),
      				r;
      		 
      		if (idx1 > idx2) {
      			r = 1;
      		}
      		else {
      			r = -1;
      		}
      		return r;
      	});
      	
      	for (i = 0, len = r.length; i < len; i++) {
      		this.fireEvent("add", this, [r[i]], this.data.indexOf(r[i]));
      	}
      	
      	/*
      	this.suspendEvents();
      	this.removeNodeDescendants(loaded_node);
      	this.add(r);
      	this.applyTreeSort();
      	this.resumeEvents();
      	this.fireEvent("datachanged", this);
      	*/
      }
    }
    this.fireEvent("load", this, r, options);
    if (options.callback) {
      options.callback.call(options.scope || this, r, options, true);
    }
  },
	
	// Tree support methods.
	// -----------------------------------------------------------------------------------------------

	/**
	 * Sorts store data with respect to nodes parent-child relation. Every child node will be 
	 * positioned after its parent.
	 *
	 * @access public
	 */
	applyTreeSort : function()
	{
		var i, len, temp,
				rec, records = [],
				roots = this.getRootNodes();
				
		// Sorting data
		for (i = 0, len = roots.length; i < len; i++) {
			rec = roots[i];
			records.push(rec);
			this.collectNodeChildrenTreeSorted(records, rec); 
		}
		
		if (records.length > 0) {
			this.data.clear();
			this.data.addAll(records);
		}
		
		// Sorting the snapshot if one present.
		if (this.snapshot && this.snapshot !== this.data) {
			temp = this.data;
			this.data = this.snapshot;
			this.snapshot = null; 
			this.applyTreeSort();
			this.snapshot = this.data;
			this.data = temp;
		}
	},
	
	/**
	 * Recusively collects rec descendants and adds them to records[] array.
	 *
	 * @access private
	 * @param {Record[]} records
	 * @param {Record} rec
	 */
	collectNodeChildrenTreeSorted : function(records, rec)
	{
		var i, len,
				child, 
				children = this.getNodeChildren(rec);
				
		for (i = 0, len = children.length; i < len; i++) {
			child = children[i];
			records.push(child);
			this.collectNodeChildrenTreeSorted(records, child); 
		}
	},
	
	/**
	 * Returns current active node.
	 * 
	 * @access public
	 * @return {Record}
	 */
	getActiveNode : function()
	{
		return this.active_node;
	},
	
	/**
	 * Sets active node.
	 * 
	 * @access public
	 * @param {Record} rc Record to set active. 
	 */
	setActiveNode : function(rc)
	{
		if (this.active_node !== rc) {
			if (rc) {
				if (this.data.indexOf(rc) != -1) {
					if (this.fireEvent('beforeactivenodechange', this, this.active_node, rc) !== false) {
						this.active_node = rc;
						this.fireEvent('activenodechange', this, this.active_node, rc);
					}
				}
				else {
					throw "Given record is not from the store.";
				}
			}
			else {
				if (this.fireEvent('beforeactivenodechange', this, this.active_node, rc) !== false) {
					this.active_node = rc;
					this.fireEvent('activenodechange', this, this.active_node, rc);
				}
			}
		}
	},
	 
	/**
	 * Returns true if node is expanded.
	 *
	 * @access public
	 * @param {Record} rc
	 */
	isExpandedNode : function(rc)
	{
		return rc.ux_maximgb_treegrid_expanded === true;
	},
	
	/**
	 * Sets node expanded flag.
	 *
	 * @access private
	 */
	setNodeExpanded : function(rc, value)
	{
		rc.ux_maximgb_treegrid_expanded = value;
	},
	
	/**
	 * Returns true if node's ancestors are all expanded - node is visible.
	 *
	 * @access public
	 * @param {Record} rc
	 */
	isVisibleNode : function(rc)
	{
		var i, len,
				ancestors = this.getNodeAncestors(rc),
				result = true;
		
		for (i = 0, len = ancestors.length; i < len; i++) {
			result = result && this.isExpandedNode(ancestors[i]);
			if (!result) {
				break;
			}
		}
		
		return result;
	},
	
	/**
	 * Returns true if node is a leaf.
	 *
	 * @access public
	 * @return {Boolean}
	 */
	isLeafNode : function(rc)
	{
		return rc.get(this.leaf_field_name) == true;
	},
	
	/**
	 * Returns true if node was loaded.
	 *
	 * @access public
	 * @return {Boolean}
	 */
	isLoadedNode : function(rc)
	{
		var result;
		
		if (rc.ux_maximgb_treegrid_loaded !== undefined) {
			result = rc.ux_maximgb_treegrid_loaded
		}
		else if (this.isLeafNode(rc) || this.hasChildNodes(rc)) {
			result = true;
		}
		else {
			result = false;
		}
		
		return result;
	},
	
	/**
	 * Sets node loaded state.
	 *
	 * @access private
	 * @param {Record} rc
	 * @param {Boolean} value
	 */
	setNodeLoaded : function(rc, value)
	{
		rc.ux_maximgb_treegrid_loaded = value;
	},
	
	/**
	 * Returns node's children offset.
	 *
	 * @access public
	 * @param {Record} rc
	 * @return {Integer} 
	 */
	getNodeChildrenOffset : function(rc)
	{
		return rc.ux_maximgb_treegrid_offset || 0;
	},
	
	/**
	 * Sets node's children offset.
	 *
	 * @access private
	 * @param {Record} rc
	 * @parma {Integer} value 
	 */
	setNodeChildrenOffset : function(rc, value)
	{
		rc.ux_maximgb_treegrid_offset = value;
	},
	
	/**
	 * Returns node's children total count
	 *
	 * @access public
	 * @param {Record} rc
	 * @return {Integer}
	 */
	getNodeChildrenTotalCount : function(rc)
	{
		return rc.ux_maximgb_treegrid_total || 0;
	},
	
	/**
	 * Sets node's children total count.
	 *
	 * @access private
	 * @param {Record} rc
	 * @param {Integer} value
	 */
	setNodeChildrenTotalCount : function(rc, value)
	{
		rc.ux_maximgb_treegrid_total = value;
	},
	
	/**
	 * Collapses node.
	 *
	 * @access public
	 * @param {Record} rc
	 * @param {Record} rc Node to collapse. 
	 */
	collapseNode : function(rc)
	{
		if (
			this.isExpandedNode(rc) &&
			this.fireEvent('beforecollapsenode', this, rc) !== false 
		) {
			this.setNodeExpanded(rc, false);
			this.fireEvent('collapsenode', this, rc);
		}
	},
	
	/**
	 * Expands node.
	 *
	 * @access public
	 * @param {Record} rc
	 */
	expandNode : function(rc)
	{
		var params;
		
		if (
			!this.isExpandedNode(rc) &&
			this.fireEvent('beforeexpandnode', this, rc) !== false
		) {
			// If node is already loaded then expanding now.
			if (this.isLoadedNode(rc)) {
				this.setNodeExpanded(rc, true);
				this.fireEvent('expandnode', this, rc);
			}
			// If node isn't loaded yet then expanding after load.
			else {
				params = {};
				params[this.paramNames.active_node] = rc.id;
				this.load({
					add : true,
					params : params,
					callback : this.expandNodeCallback,
					scope : this
				});
			}
		}
	},
	
	/**
	 * @access private
	 */
	expandNodeCallback : function(r, options, success)
	{
		var rc = this.getById(options.params[this.paramNames.active_node]);
		
		if (success && rc) {
			this.setNodeLoaded(rc, true);
			this.setNodeExpanded(rc, true);
			this.fireEvent('expandnode', this, rc);
		}
		else {
			this.fireEvent('expandnodefailed', this, options.params[this.paramNames.active_node], rc);
		}
	},
	
	/**
	 * Returns loaded node id from the load options.
	 *
	 * @access public
	 */
	getLoadedNodeIdFromOptions : function(options)
	{
		var result = null;
		if (options && options.params && options.params[this.paramNames.active_node]) {
			result = options.params[this.paramNames.active_node];
		}
		return result;
	},
	
	/**
	 * Returns start offset from the load options.
	 */
	getPageOffsetFromOptions : function(options)
	{
		var result = 0;
		if (options && options.params && options.params[this.paramNames.start]) {
			result = parseInt(options.params[this.paramNames.start], 10);
			if (isNaN(result)) {
				result = 0;
			}
		}
		return result;
	},
	
	// Public
	hasNextSiblingNode : function(rc)
	{
		return this.getNodeNextSibling(rc) !== null;
	},
	
	// Public
	hasPrevSiblingNode : function(rc)
	{
		return this.getNodePrevSibling(rc) !== null;
	},
	
	// Public
	hasChildNodes : function(rc)
	{
		return this.getNodeChildrenCount(rc) > 0;
	},
	
	// Public
	getNodeAncestors : function(rc)
	{
		var ancestors = [],
				parent;
		
		parent = this.getNodeParent(rc);
		while (parent) {
			ancestors.push(parent);
			parent = this.getNodeParent(parent);	
		}
		
		return ancestors;
	},
	
	// Public
	getNodeChildrenCount : function(rc)
	{
		return this.getNodeChildren(rc).length;
	},
	
	// Public
	getNodeNextSibling : function(rc)
	{
		var siblings,
				parent,
				index,
				result = null;
				
		parent = this.getNodeParent(rc);
		if (parent) {
			siblings = this.getNodeChildren(parent);
		}
		else {
			siblings = this.getRootNodes();
		}
		
		index = siblings.indexOf(rc);
		
		if (index < siblings.length - 1) {
			result = siblings[index + 1];
		}
		
		return result;
	},
	
	// Public
	getNodePrevSibling : function(rc)
	{
		var siblings,
				parent,
				index,
				result = null;
				
		parent = this.getNodeParent(rc);
		if (parent) {
			siblings = this.getNodeChildren(parent);
		}
		else {
			siblings = this.getRootNodes();
		}
		
		index = siblings.indexOf(rc);
		if (index > 0) {
			result = siblings[index - 1];
		}
		
		return result;
	},
	
	// Abstract tree support methods.
	// -----------------------------------------------------------------------------------------------
	
	// Public - Abstract
	getRootNodes : function()
	{
		throw 'Abstract method call';
	},
	
	// Public - Abstract
	getNodeDepth : function(rc)
	{
		throw 'Abstract method call';
	},
	
	// Public - Abstract
	getNodeParent : function(rc)
	{
		throw 'Abstract method call';
	},
	
	// Public - Abstract
	getNodeChildren : function(rc)
	{
		throw 'Abstract method call';
	},
	
	// Public - Abstract
	addToNode : function(parent, child)
	{
		throw 'Abstract method call';
	},
	
	// Public - Abstract
	removeFromNode : function(parent, child)
	{
		throw 'Abstract method call';
	},
	
	// Paging support methods.
	// -----------------------------------------------------------------------------------------------
	/**
	 * Returns top level node page offset.
	 *
	 * @access public
	 * @return {Integer}
	 */
	getPageOffset : function()
	{
		return this.page_offset;
	},
	
	/**
	 * Returns active node page offset.
	 *
	 * @access public
	 * @return {Integer}
	 */
	getActiveNodePageOffset : function()
	{
		var result;
		
		if (this.active_node) {
			result = this.getNodeChildrenOffset(this.active_node);
		}
		else {
			result = this.getPageOffset();
		}
		
		return result;
	},
	
	/**
	 * Returns active node children count.
	 *
	 * @access public
	 * @return {Integer}
	 */
	getActiveNodeCount : function()
	{
		var result;
		
		if (this.active_node) {
			result = this.getNodeChildrenCount(this.active_node);
		}
		else {
			result = this.getRootNodes().length;
		}
		
		return result;
	},
	
	/**
	 * Returns active node total children count.
	 *
	 * @access public
	 * @return {Integer}
	 */
	getActiveNodeTotalCount : function()
	{
		var result;
		
		if (this.active_node) {
			result = this.getNodeChildrenTotalCount(this.active_node);
		}
		else {
			result = this.getTotalCount();
		}
		
		return result;	
	}
	
});